#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include <common/macro/build.h>
#include <common/macro/debug.h>
#include <common/io.h>

/** 
* @brief Open a new file.
* 
* @param fn Filename.
* @param flags Flags to be used, equivalent to open() flags.
* @param mode Mode to be used, equivalent to open() mode.
* 
* @return New iohandle_t.
*/
API_LIBLOCAL inline iohandle_t io_open(const char *fn, int flags, int mode) { 
	char *op;
	int fd;

	assert(fn != NULL);

	fd = open(fn, flags, mode);
	if (flags & O_RDWR)
		op = "r+b";
	else if (flags & O_WRONLY)
		op = "wb";
	else
		op = "rb";
	return (iohandle_t)fdopen(fd, op);
}

/** 
* @brief Convenience function to open a file in read/write/create mode.
* 
* @param fn Filename.
* 
* @return New iohandle_t.
*/
API_LIBLOCAL inline iohandle_t io_open_rwc(const char *fn) { 
	assert(fn != NULL);
	return io_open(fn, O_RDWR|O_CREAT, 0666);
}

/** 
* @brief Convenience function to open a file in read-only mode.
* 
* @param fn Filename.
* 
* @return New iohandle_t.
*/
API_LIBLOCAL inline iohandle_t io_open_r(const char *fn) { 
	assert(fn != NULL);
	return io_open(fn, O_RDONLY, 0666);
}

/** 
* @brief Convenience function to open a file in write/create mode.
* 
* @param fn Filename.
* 
* @return New iohandle_t.
*/
API_LIBLOCAL inline iohandle_t io_open_w(const char *fn) { 
	assert(fn != NULL);
	return io_open(fn, O_WRONLY|O_CREAT, 0666);
}

/** 
* @brief Read binary data from the file. Wrapper around fread().
* 
* @param ptr Destination buffer.
* @param size Size of an element.
* @param nmemb Number of elements.
* @param io IO handle.
* 
* @return Same as fread().
*/
API_LIBLOCAL inline size_t io_read(void *ptr, size_t size, size_t nmemb, iohandle_t io) {
	assert(ptr != NULL);
	assert(io_valid(io));

	fflush((FILE*)io);
	return fread(ptr, size, nmemb, (FILE*)io);
}

/** 
* @brief Write binary data into a file. Wrapper around fwrite().
* 
* @param ptr Source buffer.
* @param size Size of an element.
* @param nmemb Number of elements.
* @param io IO handle.
* 
* @return Same as fwrite().
*/
API_LIBLOCAL inline size_t io_write(const void *ptr, size_t size, size_t nmemb, iohandle_t io) {
	size_t out;

	assert(ptr != NULL);
	assert(io_valid(io));

	out = fwrite(ptr, size, nmemb, (FILE*)io);
	fflush((FILE*)io);
	return out;
}

/** 
* @brief Seeks to a position in file. Wrapper around fseek().
* 
* @param io
* @param offset
* @param whence
* 
* @return Same as fseek().
*/
API_LIBLOCAL inline int io_seek(iohandle_t io, off_t offset, int whence) {
	assert(io_valid(io));

	return fseek((FILE*)io, offset, whence);
}

/** 
* @brief Returns information about current position in file.
* Wrapper around ftello().
* 
* @param io
* 
* @return 
*/
API_LIBLOCAL inline off_t iohandle_tell(iohandle_t io) {
	assert(io_valid(io));

	return ftello((FILE*)io);
}

/** 
* @brief Closes the given handle.
* 
* @param io
* 
* @return Zero if success, -1 otherwise.
*/
API_LIBLOCAL inline int io_close(iohandle_t io) {
	assert(io_valid(io));

	return fclose((FILE*)io);
}

/** 
* @brief Removes a file or directory. Wrapper aroudn remove()
* 
* @param filename
* 
* @return Same as remove().
*/
API_LIBLOCAL inline int io_remove(const char *filename) {
	return remove(filename);
}

/** 
* @brief Syncs data (but not metadata) of the given handle.
* 
* @param io
* 
* @return 
*/
API_LIBLOCAL inline int io_sync(iohandle_t io) {
	assert(io_valid(io));

	fflush((FILE*)io);
	return fdatasync(fileno((FILE*)io));
}

/** 
* @brief Truncates the file to the given size. Wrapper around ftruncate().
* 
* @param io
* @param length
* 
* @return Same as ftruncate().
*/
API_LIBLOCAL inline int io_truncate(iohandle_t io, off_t length) {
	assert(io_valid(io));

	return ftruncate(fileno((FILE*)io), length);
}

/** 
* @brief Verifies whether end of file has been reached.
* 
* @param io
* 
* @return Zero if not EOF, non-zero otherwise.
*/
API_LIBLOCAL inline int io_feof(iohandle_t io) {
	assert(io_valid(io));

	return feof((FILE*)io);
}

/** 
* @brief Verifies validity of the IO handle. Useful for asserts.
* 
* @param io
* 
* @return Zero if not valid, non-zero otherwise.
*/
API_LIBLOCAL inline int io_valid(iohandle_t io) {
	return ((void*)io != NULL);
}
